package org.aspire.game;

import org.aspire.data.Renderable;
import org.aspire.data.Renderer;
import org.aspire.data.Updateable;
import org.aspire.graphics.Frame;
import org.aspire.math.geom.Dimension;
import org.aspire.math.geom.Point;
import org.aspire.phys.Moving;

/******************************************************************************
 * A sprite is an entity that moves around the screen over a period of time. It
 * has a graphical look and its own behavior.
 * 
 * @author JPACarabuena
 *****************************************************************************/
public class Sprite extends Entity implements Renderable, Updateable, Moving {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================

	/**************************************************************************
	 * The image frame for this sprite.
	 *************************************************************************/
	private Frame frame;
	
	/**************************************************************************
	 * The background where the sprite will be embedded.
	 *************************************************************************/
	private Background background;
	
	/**************************************************************************
	 * The horizontal speed of this sprite.
	 *************************************************************************/
	private double horizontalSpeed;
	
	/**************************************************************************
	 * The vertical speed of this sprite.
	 *************************************************************************/
	private double verticalSpeed;
	
	/**************************************************************************
	 * The x-coordinate of this sprite from the last move.
	 *************************************************************************/
	private double oldX;
	
	/**************************************************************************
	 * The y-coordinate of this sprite from the last move.
	 *************************************************************************/
	private double oldY;
	
	// ========================================================================
	// Constructors
	// ========================================================================
	
	/**************************************************************************
	 * Construct a new sprite with the specified graphical look and location.
	 * 
	 * @param frame the frame for this sprite.
	 * @param location the x and y coordinate of this sprite.
	 *************************************************************************/
	public Sprite(Frame frame, Point location) {
		super(new Dimension(frame.getWidth(), frame.getHeight()), location);
		this.frame = frame;
	}
	
	/**************************************************************************
	 * Construct a new sprite with the specified graphical look and default
	 * location.
	 * 
	 * @param frame the frame for this sprite.
	 *************************************************************************/
	public Sprite(Frame frame) {
		super(new Dimension(frame.getWidth(), frame.getHeight()), new Point());
		this.frame = frame;
	}
	
	/**************************************************************************
	 * Construct a new sprite without a graphical look.
	 *************************************************************************/
	public Sprite() {
		super();
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================

	/**************************************************************************
	 * Return the graphical look for this sprite.
	 * 
	 * @return the sprite's frame.
	 *************************************************************************/
	public Frame getFrame() {
		return this.frame;
	}
	
	/**************************************************************************
	 * Return the background where this sprite is embedded.
	 * 
	 * @return the background.
	 *************************************************************************/
	public Background getBackground() {
		return this.background;
	}
	
	/**************************************************************************
	 * Returns the old x-coordinate of the sprite during the last movement.
	 * 
	 * @return	the old x-coordinate.
	 *************************************************************************/
	public double getOldX() {
		return this.oldX;
	}
	
	/**************************************************************************
	 * Returns the old y-coordinate of the sprite during the last movement.
	 * 
	 * @return the old y-coordinate.
	 *************************************************************************/
	public double getOldY() {
		return this.oldY;
	}
	
	/**************************************************************************
	 * Sets the graphical look for this sprite.
	 * 
	 * @param frame the specified frame.
	 *************************************************************************/
	public void setFrame(Frame frame) {
		this.frame = frame;
	}
	
	/**************************************************************************
	 * Sets the background where this sprite will be embedded to the specified
	 * background.
	 * 
	 * @param background the new background. 
	 *************************************************************************/
	public void setBackground(Background background) {
		this.background = background;
	}
	
	public void setLocation(double x, double y) {
		this.setX(x);
		this.setY(y);
	}
	
	public void setSpeed(double dx, double dy) {
		this.setHorizontalSpeed(dx);
		this.setVerticalSpeed(dy);
	}
	
	// ========================================================================
	// Override Methods
	// ========================================================================

	@Override
	public void update(long elapsedTime) {
		this.move(this.getHorizontalSpeed() * elapsedTime, 
				this.getVerticalSpeed() * elapsedTime);
	}

	@Override
	public void render(Renderer r) {
		r.drawImage(this.frame, (int) this.getX(), (int) this.getY());
	}

	@Override
	public double getHorizontalSpeed() {
		return this.horizontalSpeed;
	}

	@Override
	public double getVerticalSpeed() {
		return this.verticalSpeed;
	}

	@Override
	public void setHorizontalSpeed(double dx) {
		this.horizontalSpeed = dx;
	}

	@Override
	public void setVerticalSpeed(double dy) {
		this.verticalSpeed = dy;
	}
	
	@Override
	public void setX(double x) {
		this.oldX = x;
		super.setX(x);
	}
	
	@Override
	public void setY(double y) {
		this.oldY = y;
		super.setY(y);
	}

	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Attempts to move this sprite to specified <code>xs</code>,
	 * <code>ys</code> location, if the sprite is right on specified location,
	 * this method will return true.
	 * <p>
	 * 
	 * For example :
	 * 
	 * <pre>
	 *    Sprite s;
	 *    public void update(long elapsedTime) {
	 *       // move sprite to 100, 100 with speed 0.1 pixel in a millisecond
	 *       if (s.moveTo(elapsedTime, 100, 100, 0.1) {
	 *          // sprite has arrived to 100, 100
	 *       }
	 *    }
	 * </pre>
	 *************************************************************************/
	public boolean moveTo(long elapsedTime, double xs, double ys, 
			double speed) {
		int x = (int) this.getX();
		int y = (int) this.getY();
		
		if(x == xs && y == ys) {
			return true;
		}
		
		double angle = 90 + Math.toDegrees(Math.atan2(ys - y, xs - x));
		double radians = Math.toRadians(angle);
		
		double vx = Math.sin(radians) * speed * elapsedTime;
		double vy = -Math.cos(radians) * speed * elapsedTime;
		
		boolean arriveX = false;
		boolean arriveY = false;
		
		if(vx != 0) {
			if(vx > 0) {
				if(x + vx >= xs) {
					vx = xs - x;
					arriveX = true;
				}
			} else {
				if(x + vx <= xs) {
					vx = xs - x;
					arriveX = true;
				}
			}
		} else if(x == xs) {
			arriveX = true;
		}
		
		if(vy != 0) {
			if(vy > 0) {
				if(y + vy >= ys) {
					vy = ys - y;
					arriveY = true;
				}
			} else {
				if(y + vy <= ys) {
					vy = ys - y;
					arriveY = true;
				}
			}
		} else if(y == ys) {
			arriveY = true;
		}
		
		this.move(vx, vy);
		
		return (arriveX && arriveY);
	}
	
	/**************************************************************************
	 * Move the sprite as far as delta x (dx) and delta y (dy).
	 * 
	 * @param dx	the change in x-coordinate.
	 * @param dy	the change in y-coordinate.
	 *************************************************************************/
	public void move(double dx, double dy) {
		if(dx != 0 || dy != 0) {
			this.oldX = this.getX();
			this.setX(this.getX() + dx);
			this.oldY = this.getY();
			this.setY(this.getY() + dy);
		}
	}
	
	public void moveX(double dx) {
		if(dx != 0) {
			this.oldX = this.getX();
			this.setX(this.getX() + dx);
		}
	}
	
	public void moveY(double dy) {
		if(dy != 0) {
			this.oldY = this.getY();
			this.setY(this.getY() + dy);
		}
	}
	
	public void forceX(double x) {
		this.setX(x);
	}
	
	public void forceY(double y) {
		this.setY(y);
	}
	
	public void setMovement(double speed, double angle) {
		double radians = Math.toRadians(angle);
		this.setSpeed(Math.sin(radians) * speed, -Math.cos(radians) * speed);
	}
	
	public void addHorizontalSpeed(long elapsedTime, double accel, 
			double maxSpeed) {
		if(accel == 0 || elapsedTime == 0) {
			return;
		}
		
		this.setHorizontalSpeed(this.getHorizontalSpeed() + (accel * 
				elapsedTime));
		
		if(accel < 0) {
			if(this.getHorizontalSpeed() < maxSpeed) {
				this.setHorizontalSpeed(maxSpeed);
			}
		} else {
			if(this.getHorizontalSpeed() > maxSpeed) {
				this.setHorizontalSpeed(maxSpeed);
			}
		}
	}

	public void addVerticalSpeed(long elapsedTime, double accel, 
			double maxSpeed) {
		if(accel == 0 || elapsedTime == 0) {
			return;
		}
		
		this.setVerticalSpeed(this.getVerticalSpeed() + (accel * 
				elapsedTime));
		
		if(accel < 0) {
			if(this.getVerticalSpeed() < maxSpeed) {
				this.setVerticalSpeed(maxSpeed);
			}
		} else {
			if(this.getVerticalSpeed() > maxSpeed) {
				this.setVerticalSpeed(maxSpeed);
			}
		}
	}
	
	public double getCenterX() {
		return this.getX() + (this.getWidth() / 2);
	}
	
	public double getCenterY() {
		return this.getY() + (this.getHeight() / 2);
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}